/***************************************************************/ /* CORR,FREQ,SQLを用いた頻出アイテムセット抽出例示プログラム */ /* 2011 SASユーザ−総会プレゼンテーション関連資料 */ /* 「SASによる頻出アイテムセットの抽出」 */ /* 谷岡日出男 データマインテック株式会社 を参照 */ /* 2011/06/13, Copyright by H.Tanioka, Data Mine Tech Ltd. */ /***************************************************************/ /* Frequent itemsets Mining Datasets repository (http://fimi.ua.ac.be/data/) */ filename in "C:\Users\hideo\Downloads"; /* <== Verify */ data form1; infile in(T10I4D100K.DAT) missover; id=put(_n_,z8.); input item @; array x {*} $3 x1-x29; i=1; do while(item ne .); x{i}=put(item,z3.); input item @; if item=. then do; output; return; end; i+1; end; keep id x:; run; /* form1 → form2 */ data form2; set form1; array x {*} x1-x29; do i=1 to dim(x); if x{i}="" then leave; item=x{i}; output; end; keep id item; run; /* how many items? */ proc freq data=form2;table item/noprint out=item(keep=item count);run; /* form2 → form3 */ /* count変数を追加 */ data form2mod; length id $8 item $4 count 8; set item(in=in1) form2; if in1 then count=.; else count=1; run; proc transpose data=form2mod prefix=I_ out=form3(drop=_name_ _label_ where=(id ne "")); by id; id item; var count; run; /* (1) CORRを使う例 */ /* CORR k=2,mincnt=1 */ data _null_; now=datetime(); call symput("START",put(now,best12.)); run; /* form3形式データから平方和積和行列を求める */ proc corr data=form3 sscp noprint nocorr out=sscp(drop=intercept where=(_type_="SSCP" and _name_ contains "I_")); var I_:; run; /* 結果行列の上三角部分を読んで(item1,item2,count)のリスト結果に変換 */ data corr_result; set sscp; length item1 item2 $4; array I_ {*} I_:; do i=1 to dim(I_); if i>_n_ and I_{i} ne . then do; item1=substr(_name_,3); item2=substr(vname(I_{i}),3); count=I_{i}; output; end; end; keep item1 item2 count; run; /* 重複を削除して完成 */ proc sort data=corr_result nodupkey out=xxx;by item1 item2;run; data _null_; now=datetime(); call symput("FINISH",put(now,best12.)); run; data _null_; keika_finish=intck("SECOND",&START,&FINISH); put "CORR2 " keika_finish=; run; /* サブルーティンマクロ */ %macro CORR3; data _null_; now=datetime(); call symput("START",put(now,best12.)); run; /* まず、最小件数条件を満たさないアイテムを特定する */ proc means data=form3 noprint; var I_:; output out=out_I sum=; run; proc transpose data=out_I out=item(rename=(_name_=item col1=count)); var I_:; run; /* 最小件数条件を満たさない組合せをマクロ変数D1,D2,...,Ddropnに定義 */ data _null_; set item end=eee; if count<&mincnt. then do; n+1; drop=compress("D"||put(n,best12.)); call symput(drop,item); end; if eee then call symput("DROP_N",compress(put(n,best12.))); run; /* form3データからアイテム変数を削除 */ data form3d; set form3(drop= %do i=1 %to &DROP_N.; %str( &&D&i ) %end; ); run; /* 2つのアイテムセットを抽出 */ proc corr data=form3d sscp noprint nocorr out=sscpd(drop=intercept where=(_type_="SSCP" and _name_ contains "I_")); var I_:; run; /* 最小件数条件を満たす2つのアイテムセットを抽出する */ data yuko_c2; set sscpd; mincnt=&mincnt.; length item1 item2 $4; array I_ {*} I_:; do i=1 to dim(I_); if i>_n_ and I_{i}>=mincnt then do; item1=substr(_name_,3);item2=substr(vname(I_{i}),3); count=I_{i}; output; end; end; keep item1 item2 count; run; /* 重複を削除して2つの有効なアイテムセット完成 */ proc sort data=yuko_c2 nodupkey;by item1 item2;run; /* 有効な2つのアイテムセットをフォーマット定義 */ data yuko_c2_fmt; set yuko_c2 end=eee; fmtname="C2YUKO";type="C"; start=compress(item1||"_"||item2);end=start;label="1";output; if eee then do; start="*";end=start;hlo="O";label="";output; end; run; proc format cntlin=yuko_c2_fmt;run; /* form1形式データからform2形式の有効条件を満たす2つのアイテム組合せと件数を計算する。*/ /* ただし、2つのアイテム組合せを表す1つの変数itemとする(例:item1="A",item2="B"ならitem="A_B"とする) */ data Comb2; set form1; count=1; array x {*} x:; if x2="" then delete; i=1;j=i+1; do while(i<=dim(x)-1); if x{i} ne "" then do while(j<=dim(x)); item=compress(x{i}||"_"||x{j}); if x{j} ne "" and put(compress(x{i}||"_"||x{j}),$C2YUKO.)="1" then output; j+1; end; i+1;j=i+1; end; keep id item count; run; /* 有効な2アイテムの組合せを表す変数itemをform3形式のデータに変換する */ /* このときこれらの変数名をD_で始まるように命名する */ proc transpose data=Comb2 prefix=D_ out=Comb2_form3(drop=_name_); by id; id item; var count; run; /* 有効な1個のアイテムを表す変数(I_で始まる)と有効な2個のアイテム組合せを表す変数(D_で始まる) */ /* を取引IDをキーとしてマージする */ data form3_2; merge form3d Comb2_form3(in=in1); by id; if in1; run; /* VARステートメントとWITHステートメントを用いて平方和積和行列を計算 */ proc corr data=form3_2 sscp noprint nocorr out=sscp_2(drop=intercept where=(_type_="SSCP" and _name_ contains "D_")); var I_:; with D_:; run; /* 結果を読んで最小件数条件に合う3つのアイテム組合せを抽出する */ /* ただし、後で重複を削除しやすいように、item1,item2,item3の並びが昇順になるように調整する */ data corr_result2; set sscp_2; length item1 item2 item3 $4; array I_ {*} I_:; do i=1 to dim(I_); if I_{i}>=&mincnt. then do; item_1=scan(_name_,2,"_"); item_2=scan(_name_,3,"_"); item_3=substr(vname(I_{i}),3); if item_3=item_1 or item_3=item_2 then delete; /* 並べ替え */ if item_1<=item_2 and item_1<=item_3 and item_2<=item_1 then do; item1=item_1;item2=item_2;item3=item_3; end; else if item_1<=item_2 and item_1<=item_3 and item_1<=item_2 then do; item1=item_1;item2=item_1;item3=item_2; end; else if item_2<=item_3 and item_2<=item_3 and item_3<=item_1 then do; item1=item_2;item2=item_3;item3=item_1; end; else if item_2<=item_3 and item_2<=item_3 and item_1<=item_3 then do; item1=item_2;item2=item_1;item3=item_3; end; else if item_3<=item_1 and item_3<=item_2 and item_1<=item_2 then do; item1=item_3;item2=item_1;item3=item_2; end; else if item_3<=item_1 and item_3<=item_2 and item_2<=item_1 then do; item1=item_3;item2=item_2;item3=item_1; end; count=I_{i}; output; end; end; keep item1 item2 item3 count; run; /* 重複を削除して完成 */ proc sort data=corr_result2 nodupkey;by item1 item2 item3;run; data _null_; now=datetime(); call symput("FINISH",put(now,best12.)); run; data _null_; keika_finish=intck("SECOND",&START,&FINISH); put "CORR3 &mincnt. " keika_finish=; run; %mend CORR3; /* 3アイテム mincnt=1000 */ %let mincnt=1000;%CORR3 /* 3アイテム mincnt=500 */ %let mincnt=500;%CORR3 /* 3アイテム mincnt=100 */ %let mincnt=100;/*%CORR3*/ /* <== 注意:実行時間かかるためコメントアウトしています */ /* (2) FREQを使う例 */ /* FREQ k=2,mincnt=1 */ data _null_; now=datetime(); call symput("START",put(now,best12.)); run; data Comb2; set form1; array x {*} x:; if x2="" then delete; i=1;j=i+1; do while(i<=dim(x)-1); item1=x{i}; if x{i} ne "" then do while(j<=dim(x)); item2=x{j}; if x{j} ne "" then output; j+1; end; i+1;j=i+1; end; keep id item1 item2; run; proc freq data=Comb2; tables item1*item2/noprint out=Comb2count(keep=item1 item2 count); run; data _null_; now=datetime(); call symput("FINISH",put(now,best12.)); run; data _null_; keika_finish=intck("SECOND",&START,&FINISH); put "FREQ2 " keika_finish=; run; %macro FREQ3; data _null_; now=datetime(); call symput("START",put(now,best12.)); run; data Comb3; set form1; array x {*} x:; if x3="" then delete; i=1;j=i+1;k=j+1; do while(i<=dim(x)-2); item1=x{i}; if x{i} ne "" then do while(j<=dim(x)-1); item2=x{j}; if x{j} ne "" then do while(k<=dim(x)); item3=x{k}; if x{k} ne "" then output; k+1; end; j+1;k=j+1; end; i+1;j=i+1;k=j+1; end; keep id item1 item2 item3; run; proc sort data=Comb3 out=Comb3_2;by item1;run; proc freq data=Comb3_2; by item1; tables item2*item3/noprint out=Comb3count(keep=item1 item2 item3 count where=(COUNT>=&mincnt.)); run; data _null_; now=datetime(); call symput("FINISH",put(now,best12.)); run; data _null_; keika_finish=intck("SECOND",&START,&FINISH); put "FREQ3 &mincnt. " keika_finish=; run; %mend FREQ3; /* FREQ k=3,mincnt=1000 */ %let mincnt=1000;%FREQ3 /* FREQ k=3,mincnt=500 */ %let mincnt=500;%FREQ3 /* FREQ k=3,mincnt=100 */ %let mincnt=100;%FREQ3 /* (3) SQLを使う例 */ /* SQL k=2,mincnt=1 */ data _null_; now=datetime(); call symput("START",put(now,best12.)); run; proc sql; create table item as select item, count(*) as count from form2 group by item; quit; /* 2つのアイテムセット候補(C2)を作成する */ proc sql; create table C2 as select a.item as item1, b.item as item2 from item as a, item as b where a.item < b.item; quit; /* データベースを検索し候補アイテムセット該当件数を調べる */ /* C2をフォーマット化 */ data C2fmt; set C2 end=eee; length label $13; fmtname="C2P";type="C"; start=compress(item1||"."||item2);end=start;label=compress("P"||put(_N_,best12.));output; if eee then do; start="***";end=start;hlo="O";label="";output; end; run; proc format cntlin=C2fmt;run; data _null_; call symput("NOBS",compress(put(nobs,best12.))); stop; set C2 nobs=nobs; run; /* form1を検索 */ data C2count; set form1 end=eee; if mod(_n_,1000)=1 then put _n_=; array x {*} x:; if x2="" then delete; array P {*} P1-P&NOBS.; i=1;j=i+1; do while(i<=dim(x)-1); if x{i} ne "" then do while(j<=dim(x)); if x{j} ne "" and put(compress(x{i}||"."||x{j}),$C2P.) ne "" then do; P{input(substr(put(compress(x{i}||"."||x{j}),$C2P.),2),best12.)}+1; end; j+1; end; i+1;j=i+1; end; if eee then output; keep P:; run; proc transpose data=C2count out=C2count_list(rename=(col1=count)); var P:; run; /* 逆変換をフォーマット化 */ data C2fmt2; set C2 end=eee; length label $13; fmtname="C2Q";type="C"; start=compress("P"||put(_N_,best12.));end=start;label=compress(item1||"."||item2);output; if eee then do; start="***";end=start;hlo="O";label="";output; end; run; proc format cntlin=C2fmt2;run; data C2count_list2; length item1 item2 $3 count 8; set C2count_list(where=(count ne .)); item1=scan(put(_name_,$C2Q.),1,"."); item2=scan(put(_name_,$C2Q.),2,"."); keep item1 item2 count; run; /* 完成 */ proc sort data=C2count_list2;by item1 item2;run; data _null_; now=datetime(); call symput("FINISH",put(now,best12.)); run; data _null_; keika_finish=intck("SECOND",&START,&FINISH); put "SQL2 " keika_finish=; run; %macro SQL3; data _null_; now=datetime(); call symput("START",put(now,best12.)); run; /* まず、k=2,mincnt=1000 のアイテムセットを抽出する */ proc sql; create table item&mincnt. as select item, count(*) as count from form2 group by item having count>=&mincnt.; quit; /* 2つの組合せのアイテムセット候補(C2_&mincnt)を作成する */ proc sql; create table C2_&mincnt. as select a.item as item1, b.item as item2 from item&mincnt. as a, item&mincnt. as b where a.item < b.item; quit; /* データベースを検索し候補アイテムセット該当件数を調べる */ /* C2_&mincntをフォーマット化 */ data C2_&mincnt.fmt; set C2_&mincnt. end=eee; length label $13; fmtname="C2_&mincnt.P";type="C"; start=compress(item1||"."||item2);end=start;label=compress("P"||put(_N_,best12.));output; if eee then do; start="***";end=start;hlo="O";label="";output; end; run; proc format cntlin=C2_&mincnt.fmt;run; /* 候補アイテムセットの数を記録 */ data _null_; call symput("NOBS",compress(put(nobs,best12.))); stop; set C2_&mincnt. nobs=nobs; run; /* form1を検索し各候補アイテムセットの件数をカウントする */ data C2_&mincnt.count; set form1 end=eee; if mod(_n_,1000)=1 then put _n_=; array x {*} x:; if x2="" then delete; array P {*} P1-P&NOBS.; i=1;j=i+1; do while(i<=dim(x)-1); if x{i} ne "" then do while(j<=dim(x)); if x{j} ne "" and put(compress(x{i}||"."||x{j}),$C2_&mincnt.P.) ne "" then do; P{input(substr(put(compress(x{i}||"."||x{j}),$C2_&mincnt.P.),2),best12.)}+1; end; j+1; end; i+1;j=i+1; end; if eee then output; keep P:; run; proc transpose data=C2_&mincnt.count out=C2_&mincnt.count_list(rename=(col1=count)); var P:; run; /* 逆変換をフォーマット化 */ data C2_&mincnt.fmt2; set C2_&mincnt. end=eee; length label $13; fmtname="C2_&mincnt.Q";type="C"; start=compress("P"||put(_N_,best12.));end=start;label=compress(item1||"."||item2);output; if eee then do; start="***";end=start;hlo="O";label="";output; end; run; proc format cntlin=C2_&mincnt.fmt2;run; data C2_&mincnt.count_list2; length item1 item2 $3 count 8; set C2_&mincnt.count_list(where=(count>=&mincnt.)); item1=scan(put(_name_,$C2_&mincnt.Q.),1,"."); item2=scan(put(_name_,$C2_&mincnt.Q.),2,"."); keep item1 item2 count; run; /* k=2のアイテムセット抽出完了 */ proc sort data=C2_&mincnt.count_list2;by item1 item2;run; /* 上記結果からk=3の場合の候補アイテムセットを作成する */ proc sql; create table C3_&mincnt. as select a.item1 as item1, a.item2 as item2, b.item2 as item3 from C2_&mincnt.count_list2 as a, C2_&mincnt.count_list2 as b where a.item1 = b.item1 and a.item2 < b.item2; quit; /*******************************************************/ /* 抽出された3アイテム組合せ(item1,item2,item3)の中で */ /* (item1,item2)と(item1,item3)の組合せは上記SQLの */ /* where句の指定からcount_list2に明らかに存在する。 */ /* しかし(item2,item3)については、count_list2を再度 */ /* 検索しないと存在するかどうかわからない。 */ /*******************************************************/ proc sql; create table C3_&mincnt.X as select a.item1, a.item2, a.item3 from C3_&mincnt. as a, C2_&mincnt.count_list2 as b where a.item2 = b.item1 and a.item3 = b.item2; quit; /* データベースを検索し候補アイテムセット該当件数を調べる */ /* C3_&mincntをフォーマット化 */ data C3_&mincnt.fmt; set C3_&mincnt.X end=eee; length label $13; fmtname="C3_&mincnt.P";type="C"; start=compress(item1||"."||item2||"."||item3);end=start;label=compress("P"||put(_N_,best12.));output; if eee then do; start="***";end=start;hlo="O";label="";output; end; run; proc format cntlin=C3_&mincnt.fmt;run; /* 候補アイテムセットの数を記録 */ data _null_; call symput("NOBS",compress(put(nobs,best12.))); stop; set C3_&mincnt.X nobs=nobs; run; /* form1を検索し各候補アイテムセットの件数をカウントする */ data C3_&mincnt.count; set form1 end=eee; if mod(_n_,1000)=1 then put _n_=; array x {*} x:; if x3="" then delete; array P {*} P1-P&NOBS.; i=1;j=i+1;k=j+1; do while(i<=dim(x)-2); if x{i} ne "" then do while(j<=dim(x)-1); if x{j} ne "" then do while(k<=dim(x)); if x{k} ne "" and put(compress(x{i}||"."||x{j}||"."||x{k}),$C3_&mincnt.P.) ne "" then do; P{input(substr(put(compress(x{i}||"."||x{j}||"."||x{k}),$C3_&mincnt.P.),2),best12.)}+1; end; k+1; end; j+1;k=j+1; end; i+1;j=i+1;k=j+1; end; if eee then output; keep P:; run; proc transpose data=C3_&mincnt.count out=C3_&mincnt.count_list(rename=(col1=count)); var P:; run; /* 逆変換をフォーマット化 */ data C3_&mincnt.fmt2; set C3_&mincnt. end=eee; length label $13; fmtname="C3_&mincnt.Q";type="C"; start=compress("P"||put(_N_,best12.));end=start;label=compress(item1||"."||item2||"."||item3);output; if eee then do; start="***";end=start;hlo="O";label="";output; end; run; proc format cntlin=C3_&mincnt.fmt2;run; data C3_&mincnt.count_list2; length item1 item2 item3 $3 count 8; set C3_&mincnt.count_list(where=(count>=&mincnt.)); item1=scan(put(_name_,$C3_&mincnt.Q.),1,"."); item2=scan(put(_name_,$C3_&mincnt.Q.),2,"."); item3=scan(put(_name_,$C3_&mincnt.Q.),3,"."); keep item1 item2 item3 count; run; /* 完成 */ proc sort data=C3_&mincnt.count_list2;by item1 item2 item3;run; data _null_; now=datetime(); call symput("FINISH",put(now,best12.)); run; data _null_; keika_finish=intck("SECOND",&START,&FINISH); put "SQL3 &mincnt. " keika_finish=; run; %mend SQL3; /* SQL k=3, mincnt=1000 */ %let mincnt=1000;%SQL3 /* SQL k=3, minsup=500 */ %let mincnt=500;%SQL3 /* SQL k=3, minsup=100 */ %let mincnt=100;%SQL3